/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.to.aboomy.bannersample.indicator;

import com.to.aboomy.banner.Indicator;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DependentLayout;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;

/**
 * CircleIndicatorView 指示器
 *
 * @since 2021-08-05
 */
public class CircleIndicatorView extends Component
        implements Indicator, Component.DrawTask, Component.EstimateSizeListener {
    private static final int INDICATOR_DASH = 3;
    private static final float NUM3_5 = 3.5f;
    private static final float NUM2_0 = 2.0f;
    private static final float NUM0_5 = 0.5f;
    private static final int NUM10 = 10;
    private static final int NUM3 = 3;
    private static final int NUM50 = 50;
    private static final int NUM30 = 30;
    private static final int NUM2 = 2;
    private RectFloat rectF = null;
    private int pagerCount;
    private Paint indicatorPaint = null;
    private Context context;
    private int selectedPage;
    private int selectedPage2;
    private float offset;
    private float offsetPixels;
    private Color unColor = Color.RED;
    private Color selectedColor = Color.RED;
    /**
     * 是否为向左滑动
     */
    private boolean isSquare;

    /*--------------- 核心控制点大小距离参数 ---------------*/
    private float indicatorRadius;
    private float indicatorRatio = 1.0f;
    private float indicatorSelectedRadius;
    private float indicatorSelectedRatio = 1.0f;
    private float indicatorSpacing;
    private Paint slidPaint;
    private int displayWith = 0;
    /*--------------- 核心控制点大小距离参数end ---------------*/

    private boolean isAnimation = true;
    /**
     * indicator样式
     */
    private int indicatorStyle;

    private boolean isAutoLoop = false;
    /**
     * 控制在banner中的位置
     */
    private DependentLayout.LayoutConfig params;

    /**
     * 构造
     *
     * @param context context
     */
    public CircleIndicatorView(Context context) {
        this(context, null);
    }

    /**
     * 构造
     *
     * @param context context
     * @param attrSet attrSet
     */
    public CircleIndicatorView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
        this.context = context;
    }

    /**
     * 构造
     *
     * @param context context
     * @param attrSet attrSet
     * @param styleName styleName
     */
    public CircleIndicatorView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        Display display = DisplayManager.getInstance().getDefaultDisplay(context).get();
        isSquare = true;
        displayWith = display.getAttributes().width;
        indicatorRadius = AttrHelper.fp2px(NUM3_5, context);
        indicatorSelectedRadius = AttrHelper.fp2px(NUM3_5, context);
        indicatorSpacing = AttrHelper.fp2px(NUM10, context);
        rectF = new RectFloat();
        indicatorPaint = new Paint();
        indicatorPaint.setStrokeWidth(NUM3);
        indicatorPaint.setAntiAlias(true);
        indicatorPaint.setStyle(Paint.Style.STROKE_STYLE);
        indicatorPaint.setColor(Color.RED);

        slidPaint = new Paint();
        slidPaint.setAntiAlias(true);
        slidPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
        slidPaint.setColor(Color.RED);
        this.setClipEnabled(false);
        addDrawTask(this);
    }

    /**
     * 是否有最后一个过渡动画，true 没有过度动画。
     *
     * @param context context
     * @param isAnimation 是否有过度动画
     */
    public CircleIndicatorView(Context context, boolean isAnimation) {
        this(context, null);
        this.isAnimation = isAnimation;
    }

    @Override
    public void initIndicatorCount(int pagerCount2) {
        this.pagerCount = pagerCount2;
        setVisibility(pagerCount > 1 ? VISIBLE : HIDE);
        postLayout();
    }

    @Override
    public Component getView() {
        return this;
    }

    @Override

    public DependentLayout.LayoutConfig getParams() {
        if (params == null) {
            params = new DependentLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT, NUM50);
            params.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_BOTTOM);
            params.addRule(DependentLayout.LayoutConfig.CENTER_IN_PARENT);
            params.addRule(DependentLayout.LayoutConfig.BELOW);
            params.setMarginBottom(AttrHelper.vp2px(NUM10, context));
        }
        return params;
    }

    @Override
    public void setAutoStau(Boolean isAuto) {
        this.isAutoLoop = isAuto;
    }

    @Override
    public void onPageSliding(int position, float positionOffset, int positionOffsetPixels) {
        if (positionOffsetPixels > 0) {
            isSquare = true;
        } else {
            isSquare = false;
        }
        selectedPage = position;
        offset = positionOffset;
        offsetPixels = positionOffsetPixels;
        postLayout();
        invalidate();
    }

    @Override
    public void onPageSlideStateChanged(int i) {
    }

    @Override
    public void onPageChosen(int position) {
        selectedPage2 = position;
        if (isAnimation) {
            invalidate();
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        indicatorPaint.setAntiAlias(true);
        if (pagerCount == 0) {
            return;
        }
        float midY = NUM30 / NUM2_0 + NUM0_5;
        drawCircle(canvas, midY);
    }

    @Override
    public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        int width = Component.EstimateSpec.getSize(widthEstimateConfig);
        int height = Component.EstimateSpec.getSize(heightEstimateConfig);
        setEstimatedSize(
                Component.EstimateSpec.getChildSizeWithMode(width, width, Component.EstimateSpec.NOT_EXCEED),
                Component.EstimateSpec.getChildSizeWithMode(height, height, Component.EstimateSpec.NOT_EXCEED)
        );
        return true;
    }

    /**
     * getRatioSelectedRadius
     *
     * @return float
     */
    private float getRatioSelectedRadius() {
        return indicatorSelectedRadius * indicatorSelectedRatio;
    }

    private float interpolatedOffset() {
        return Math.abs(offset);
    }

    /**
     * getRatioRadius
     *
     * @return float
     */
    private float getRatioRadius() {
        return indicatorRadius * indicatorRatio;
    }

    private void drawCircle(Canvas canvas, float midY) {
        drawPagerCountCircle(canvas, midY);
        if (isAnimation) {
            float indicatorStartX = indicatorStartX(selectedPage2);
            float ratioRadius = getRatioSelectedRadius();
            float left = indicatorStartX - ratioRadius;
            float right = indicatorStartX + ratioRadius;
            rectF.modify(left, midY - indicatorSelectedRadius, right, midY + indicatorSelectedRadius);
            indicatorPaint.setColor(selectedColor);
            canvas.drawRoundRect(rectF, indicatorSelectedRadius, indicatorSelectedRadius, slidPaint);
        } else {
            float indicatorStartX = indicatorStartX(selectedPage);
            float nextIndicatorStartX = indicatorStartX(getNextPageIndex());
            float ratioRadius = getRatioSelectedRadius();
            float left = indicatorStartX - ratioRadius;
            float right = indicatorStartX + ratioRadius;
            float nextLeft = nextIndicatorStartX - ratioRadius;
            float nextRight = nextIndicatorStartX + ratioRadius;
            float leftX = left + (nextLeft - left) * interpolatedOffset();
            float rightX = right + (nextRight - right) * interpolatedOffset();
            rectF.modify(leftX, midY - indicatorSelectedRadius, rightX, midY + indicatorSelectedRadius);
            indicatorPaint.setColor(selectedColor);
            canvas.drawRoundRect(rectF, indicatorSelectedRadius, indicatorSelectedRadius, slidPaint);
        }
    }

    /**
     * 计算上一个索引值
     *
     * @param selectedPage1
     * @return int
     */
    private int getLastPageIndex(int selectedPage1) {
        int lastPageIndex = 0;
        int temp = 0;
        if (isSquare) {
            if (temp == selectedPage1) {
                lastPageIndex = pagerCount - 1;
            } else {
                lastPageIndex = selectedPage1 - 1;
            }
        } else {
            int temp2 = pagerCount - 1;
            if (temp2 == selectedPage1) {
                lastPageIndex = temp;
            } else {
                lastPageIndex = selectedPage1 + 1;
            }
        }
        return lastPageIndex;
    }

    /**
     * 计算下一个索引值
     *
     * @return int
     */
    private int getNextPageIndex() {
        int nextPageIndex = 0;
        if (offsetPixels > 0) {
            if (selectedPage + 1 > pagerCount - 1) {
                nextPageIndex = 0;
            } else {
                nextPageIndex = selectedPage + 1;
            }
        } else {
            if (isAutoLoop) {
                if (selectedPage == pagerCount - 1) {
                    nextPageIndex = 0;
                }
            } else {
                if (selectedPage - 1 < 0) {
                    nextPageIndex = pagerCount - 1;
                } else {
                    nextPageIndex = selectedPage - 1;
                }
            }
        }
        return nextPageIndex;
    }

    /**
     * 画基础的圆
     *
     * @param canvas Canvas
     * @param midY float
     */
    private void drawPagerCountCircle(Canvas canvas, float midY) {
        indicatorPaint.setColor(unColor);
        for (int ii = 0; ii < pagerCount; ii++) {
            float startCx = indicatorStartX(ii);
            float ratioIndicatorRadius = getRatioRadius();
            float left = startCx - ratioIndicatorRadius;
            float top = midY - indicatorRadius;
            float right = startCx + ratioIndicatorRadius;
            float bottom = midY + indicatorRadius;
            rectF = new RectFloat(left, top, right, bottom);
            rectF.modify(left, top, right, bottom);
            canvas.drawRoundRect(rectF, indicatorRadius, indicatorRadius, indicatorPaint);
        }
    }

    private float indicatorStartX(int index) {
        float ratioRadius = getRatioRadius();
        float ratioSelectedRadius = getRatioSelectedRadius();
        float ratioIndicatorRadius = Math.max(ratioRadius, ratioSelectedRadius);
        float centerSpacing = ratioIndicatorRadius * NUM2_0 + indicatorSpacing - NUM10;
        float ssx = (displayWith - centerSpacing * pagerCount) / NUM2;
        float centerX = ratioIndicatorRadius + getPaddingLeft() + centerSpacing * index;
        /*
           为了适配INDICATOR_DASH样式， measure 中默认多增加了 ratioIndicatorRadius - ratioRadius 的高度和宽度
           除了INDICATOR_DASH样式下，其他样式需要增加indicatorSelectedRadius一半的距离，让位置居中。
         */
        return centerX + (indicatorStyle == INDICATOR_DASH ? 0 : (ratioIndicatorRadius - ratioRadius) / NUM2) + ssx;
    }

    /**
     * 设置indicator比例，拉伸圆为矩形，控制该比例，default 1.0
     *
     * @param indicatorRatio indicatorRatio
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorRatio(float indicatorRatio) {
        if (this.indicatorRatio == this.indicatorSelectedRatio) {
            this.indicatorSelectedRatio = indicatorRatio;
        }
        this.indicatorRatio = indicatorRatio;
        return this;
    }

    /**
     * setIndicatorStyle
     *
     * @param indicatorStyle
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorStyle(int indicatorStyle) {
        this.indicatorStyle = indicatorStyle;
        return this;
    }

    /**
     * 设置indicator的圆角，同时会改变选中时的圆角，default 3.5dp
     *
     * @param indicatorRadius indicatorRadius
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorRadius(float indicatorRadius) {
        int indicatorRadiusDp = AttrHelper.vp2px(indicatorRadius, context);
        if (this.indicatorRadius == this.indicatorSelectedRadius) {
            this.indicatorSelectedRadius = indicatorRadiusDp;
        }
        this.indicatorRadius = indicatorRadiusDp;
        return this;
    }

    /**
     * 设置选中圆比例，拉伸圆为矩形，控制该比例，默认比例和indicatorRatio一致
     *
     * @param indicatorSelectedRatio indicatorSelectedRadius * indicatorSelectedRatio
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorSelectedRatio(float indicatorSelectedRatio) {
        this.indicatorSelectedRatio = indicatorSelectedRatio;
        return this;
    }

    /**
     * 设置选中的圆角，没有设置，默认和indicatorRadius值一致
     *
     * @param indicatorSelectedRadius 单位dp
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorSelectedRadius(float indicatorSelectedRadius) {
        this.indicatorSelectedRadius = AttrHelper.vp2px(indicatorSelectedRadius, context);
        return this;
    }

    /**
     * setIndicatorColor
     *
     * @param indicatorColor
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorColor(Color indicatorColor) {
        this.unColor = indicatorColor;
        return this;
    }

    /**
     * setIndicatorSelectorColor
     *
     * @param indicatorSelectorColor
     * @return CircleIndicatorView
     */
    public CircleIndicatorView setIndicatorSelectorColor(Color indicatorSelectorColor) {
        this.selectedColor = indicatorSelectorColor;
        return this;
    }
}
